#include "stdafx.h"
#include "RemoteBindingTestSuite.h"

namespace CO { namespace SpiFoundation { namespace RoomFW {

	using namespace CO::SpiControl::Protocol;

RemoteBindingTestSuite::RemoteBindingTestSuite()
{
	WSADATA WSAData;
	if (SOCKET_ERROR == ::WSAStartup(WORD MAKEWORD(2, 2), &WSAData))
	{
		throw std::exception("Failed to initialize sockets");
	}
	DynamicBindingManager::Instance();  // Make it destruct after PortRouter.  TODO:  Handle in RoomFW!!!

#if !defined( _WIN32 )
	TEST_ADD(RemoteBindingTestSuite::BindSendInvoke);
#endif
	TEST_ADD(RemoteBindingTestSuite::LoopTest);
	// TEST_ADD(RemoteBindingTestSuite::MultipleServer);
}

RemoteBindingTestSuite::~RemoteBindingTestSuite()
{
	::WSACleanup();
}

void RemoteBindingTestSuite::setup()
{
	//
	// Track initialization errors.
	//
	initialized_ = true;
	errorMessage_.clear();

	try
	{
		// Initialize the logger.  Necessary when using G_TRACE_METHOD.
		G_LOGGER.setFile(true, "RemoteBindingTestSuite.log");
		G_LOGGER.setVerbosity(CO::SpiFoundation::RoomFW::LV_ALL);
		if (!G_LOGGER.start())
		{
			throw "Failed to start logger";
		}

		// Create the server stop event.
		if (!serverStopEvent_.create(false, false, "ECHO_SERVER_STOP_EVENT"))
		{
			throw "Failed to create server stop event";
		}

		// Start the port router.
		static int const portRouterListenPort = 27000;
		if (!PortRouter::Instance().start(portRouterListenPort, "127.0.0.1", DWait))
		{
			throw "Failed to start port server";
		}

	}
	catch (std::runtime_error e)
	{
		initialized_ = false;
		errorMessage_ = e.what();
	}
	catch (const char* e)
	{
		initialized_ = false;
		errorMessage_ = e;
	}
	catch (...)
	{
		initialized_ = false;
		errorMessage_ = "Unhandled exception";
	}
}

void RemoteBindingTestSuite::tear_down()
{
	// Stop port router.
	PortRouter::Instance().stop(2000);		// 2 seconds

	// Stop server.
	serverStopEvent_.set();
	BOOST_FOREACH(SimpleChildProcessSP server, serverProcess_)
	{
		server->wait();
	}
	serverProcess_.clear();

	// Stop the logger.
	VERIFY(G_LOGGER.stop());
}

BindingSP RemoteBindingTestSuite::bindPortal(EventPortal& portal, const char* portName)
{
	G_TRACE_METHOD(RemoteBindingTestSuite::bindPortal);
	G_LOG_1_TRACE("Binding portal to %s", portName);

	BindingSP binding;
	for (size_t i = 0; i < 10; ++i)
	{
		binding = portal.dynamicBind(portName, CTID_NULL, true);
		if (binding)
		{
			break;
		}
		else
		{
			G_LOG_1_TRACE("RemoteBindingTestSuite::bindPortal ::Sleep(250) - %d", i);
			::Sleep(250);
		}
	}
	return binding;
}

// Test assert outside main.
#define TEST_ASSERT2(c) if (!(c)) {errorMessage_ = #c; return false;}

bool RemoteBindingTestSuite::verifySend(EventPortal& portal, size_t responseCount)
{
	G_TRACE_METHOD(RemoteBindingTestSuite::verifySend);

	bool retVal = true;
	portal.sendMessage(GenericTestProtocol::IN_STRING_MESSAGE1, PAYLOAD1(StringPayload, "Hello World"));
	this->verifyReply(portal, responseCount);
	return retVal;
}

bool RemoteBindingTestSuite::verifyReply(EventPortal& portal, size_t responseCount)
{
	G_TRACE_METHOD(RemoteBindingTestSuite::verifyReply);

	bool retVal = true;
	for (size_t i = 0; i < responseCount; ++i)
	{
		TEST_ASSERT2(portal.wait(DWait));
		RoomMessageSP reply = portal.getMessage();
		TEST_ASSERT2(reply);
		StringPayloadSP payload = ::boost::dynamic_pointer_cast<StringPayload>(reply->payload());
		TEST_ASSERT2(payload->value() == "Hello World");
	}
	return retVal;
}

bool RemoteBindingTestSuite::verifyInvoke(EventPortal& portal, size_t responseCount)
{
	bool retVal = true;
	RoomMessageSP reply = portal.invoke(DWait, GenericTestProtocol::IN_STRING_MESSAGE1, PAYLOAD1(StringPayload, "Hello World"));
	TEST_ASSERT2(reply);
	while (responseCount--)
	{
		TEST_ASSERT2(reply);
		StringPayloadSP payload = ::boost::dynamic_pointer_cast<StringPayload>(reply->payload());
		TEST_ASSERT2(payload->value() == "Hello World");
		if (responseCount)
		{
			TEST_ASSERT2(portal.wait(DWait));
			reply = portal.getMessage();
		}
	}
	return retVal;
}

#define TEST_ASSERT3(c) if (!(c)) {errorMessage_ = #c; return IEchoServerSP();}

IEchoServerSP RemoteBindingTestSuite::bindServer()
{
	G_TRACE_METHOD(RemoteBindingTestSuite::bindServer);

	// Create and start local server.
	IEchoServerSP server = IEchoServer::Create("LOCAL_ECHO_SERVER");
	TEST_ASSERT3(server);
	TEST_ASSERT3(server->start());

	// Bind to our server and tell it to bind to remote server.
	EventPortal portal("LOCAL_SERVER_PORTAL");
	VERIFY(portal.initialize());

	BindingSP binding = this->bindPortal(portal, "LOCAL_ECHO_SERVER_PORT_ECHO_IN");
	if (!binding)
		TEST_ASSERT3(binding);

	Sleep(1000);	// HACK!  Give the LocalEchoServer a chance to complete the remote binding.

	portal.sendMessage(GenericTestProtocol::IN_DYNAMIC_BIND,
		PAYLOAD2(DynamicBindPayload, IEchoServer::PORT_ECHO_IN, "ECHO_SERVER_PORT_ECHO_OUT"));
	return server;
}

bool RemoteBindingTestSuite::startRemoteServer(const char* name, int port)
{
	// Start the server.
	std::ostringstream cmdLine;
	cmdLine << "../EchoServer/EchoServer.exe " << name << ' ' << port;
G_LOG_2_TRACE("RemoteBindingTestSuite::startRemoteServer: EchoServer.exe(%s, %d)", name, port);
	SimpleChildProcessSP server(new SimpleChildProcess());
	TEST_ASSERT2(server->create(0, const_cast<char*>(cmdLine.str().c_str())))

	// Connnect to it.
G_LOG_0_TRACE("RemoteBindingTestSuite::startRemoteServer: connecting");
	TEST_ASSERT2(PortRouter::Instance().connect(port, "127.0.0.1"))

	return true;
}

bool RemoteBindingTestSuite::bindRemoteServer(const char* serverName, const char* clientName)
{
	std::string serverPort = serverName;
	serverPort += "_PORT_ECHO_OUT";
	std::string clientPort = clientName;
	clientPort += "_PORT_ECHO_IN";

	// Bind to client server and tell it to bind to remote server.
	EventPortal portal("TEMP_PORTAL");
	TEST_ASSERT2(portal.initialize());

	BindingSP binding = this->bindPortal(portal, clientPort.c_str());
	TEST_ASSERT2(binding);
	portal.sendMessage(GenericTestProtocol::IN_DYNAMIC_BIND,
		PAYLOAD2(DynamicBindPayload, IEchoServer::PORT_ECHO_IN, serverPort));

	return true;
}

void RemoteBindingTestSuite::BindSendInvoke()
{
	static const char* functionName = "RemoteBindingTestSuite::BindSendInvoke";
	//
	// Check initialization.
	//
	TEST_ASSERT_MSG(initialized_, errorMessage_.c_str());
	try
	{
		// Start a single remote server.
		const int defaultPort = 27001;		// Values in EchoServer.cpp and RoomFWUnitTest\RemoteBindingTestSuite.cpp must match! 
		TEST_ASSERT_MSG(this->startRemoteServer("ECHO_SERVER", defaultPort), errorMessage_.c_str());

		G_LOG_1_TRACE("TEST(%s):  Bind a portal to echo server and verify echo.", functionName);
		EventPortal portal("TEST_PORTAL");
		TEST_ASSERT_MSG(portal.initialize(), errorMessage_.c_str());

		BindingSP binding;
		binding = bindPortal(portal, "ECHO_SERVER_PORT_ECHO_IN");
		if (!binding)
		{
			static const char* bindPortalErrorString = "bindPortal(portal, \"ECHO_SERVER_PORT_ECHO_IN\")";
			G_LOG_2_ERROR("Failed to bind: %s => %s", bindPortalErrorString, errorMessage_.c_str());
		}
		TEST_ASSERT(binding);

		size_t expectedReplies = 2;
		G_LOG_1_TRACE("TEST(%s):  verifySend.", functionName);
		TEST_ASSERT_MSG(this->verifySend(portal, expectedReplies), errorMessage_.c_str());
		G_LOG_1_TRACE("TEST(%s):  verifyInvoke.", functionName);
		TEST_ASSERT_MSG(this->verifyInvoke(portal, expectedReplies), errorMessage_.c_str());

#if 1
		G_LOG_1_TRACE("TEST(%s):  Add a local echo server dynamically bound to the remote one.", functionName);
		IEchoServerSP server = this->bindServer();
		TEST_ASSERT_MSG(server, errorMessage_.c_str());
		Sleep(1000);		// Give local sever time to bind to remote server.

		//// Now we should get 4.
		//expectedReplies = 4;
		G_LOG_1_TRACE("TEST(%s):  verifySend.", functionName);
		TEST_ASSERT_MSG(this->verifySend(portal, expectedReplies), errorMessage_.c_str());
		G_LOG_1_TRACE("TEST(%s):  verifyInvoke.", functionName);
		TEST_ASSERT_MSG(this->verifyInvoke(portal, expectedReplies), errorMessage_.c_str());
#if 1	// ToDo:  Make sure this works OK, and if so delete the #if/#endif guard.
		server.reset();
#endif

#endif
		G_LOG_1_TRACE("%s: Cleanup locals", functionName);
		binding.reset();
	}
	catch (std::exception& e)
	{
		G_LOG_0_ERROR(e.what());
		TEST_FAIL(e.what());
	}
}

void RemoteBindingTestSuite::LoopTest()
{
}

void RemoteBindingTestSuite::MultipleServer()
{
	//
	// Check initialization.
	//
	TEST_ASSERT_MSG(initialized_, errorMessage_.c_str());
	try
	{
		// Start two remote servers.
		TEST_ASSERT_MSG(this->startRemoteServer("ECHO_SERVER1", 27001), errorMessage_.c_str());
		TEST_ASSERT_MSG(this->startRemoteServer("ECHO_SERVER2", 27002), errorMessage_.c_str());
		TEST_ASSERT_MSG(this->bindRemoteServer("ECHO_SERVER1", "ECHO_SERVER2"), errorMessage_.c_str());

		// TEST:  Bind a portal to echo server and verify echo.
		EventPortal portal("TEST_PORTAL");
		TEST_ASSERT(portal.initialize(), errorMessage_.c_str());

		BindingSP binding = this->bindPortal(portal, "ECHO_SERVER1_PORT_ECHO_IN");
		TEST_ASSERT(binding);

		// Now we should get 4.
		size_t expectedReplies = 2;
		TEST_ASSERT_MSG(this->verifySend(portal, expectedReplies), errorMessage_.c_str());
		TEST_ASSERT_MSG(this->verifyInvoke(portal, expectedReplies), errorMessage_.c_str());

		// Cleanup locals
		binding.reset();
	}
	catch (std::exception& e)
	{
		G_LOG_0_ERROR(e.what());
		TEST_FAIL(e.what());
	}
}

}}}
